﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using ProgressTracker.Account.Infrastructure;
using ProgressTracker.Account.Messages;
using ProgressTracker.Account.Services;
using ProgressTracker.Data.Abstract;
using ProgressTracker.Data.Entities;
using System;

namespace ProgressTracker.Tests.Account.Services
{
    [TestClass]
    public class AccountServiceTests
    {
        private Mock<IUserRepository> _userRepo;
        private Mock<ICrypt> _cryptServices;
        private Mock<IPasswordGenerator> _passwordGenerator;

        private User _testUser;

        private const string _testEmail = "test@test.com";
        private const string _testPass = "test";
        private const string _testHash = "Hash";

        [TestInitialize]
        public void Initialize()
        {
            _userRepo = new Mock<IUserRepository>();
            _cryptServices = new Mock<ICrypt>();
            _passwordGenerator = new Mock<IPasswordGenerator>();

            _testUser = new User
            {
                Email = _testEmail,
                PasswordHash = "StartingHash",
                Id = 1
            };

            _userRepo.Setup(x => x.GetUserByEmail(It.IsAny<string>())).Returns(_testUser);
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(true);
        }

        [TestMethod]
        public void Test_Login_Returns_Account_Does_Not_Exist_When_User_Does_Not_Exist()
        {
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(false).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.Login(_testEmail, _testPass);

            _userRepo.Verify();
            Assert.AreEqual(LoginResultCode.AccountDoesNotExist, result.ResultCode);
        }

        [TestMethod]
        public void Test_Login_Returns_Passwords_Do_Not_Match_If_Check_Password_Fails()
        {
            _cryptServices.Setup(x => x.CheckPassword(It.IsAny<string>(), It.IsAny<string>())).Returns(false).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.Login(_testEmail, _testPass);

            _cryptServices.Verify();
            Assert.AreEqual(LoginResultCode.PasswordDoesNotMatch, result.ResultCode);
        }

        [TestMethod]
        public void Test_Login_Returns_Success_If_Password_Is_Match()
        {
            _cryptServices.Setup(x => x.CheckPassword(It.IsAny<string>(), It.IsAny<string>())).Returns(true).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.Login(_testEmail, _testPass);

            _cryptServices.Verify();
            Assert.AreEqual(LoginResultCode.Success, result.ResultCode);
        }

        [TestMethod]
        public void Test_CreateAccount_Returns_User_Already_Exists_If_Account_Exists()
        {
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(true).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.CreateAccount(_testEmail, _testPass);

            _userRepo.Verify();
            Assert.AreEqual(AccountResultCode.UserAlreadyExists, result.ResultCode);
        }

        [TestMethod]
        public void Test_CreateAccount_Returns_Fail_To_Create_If_Create_Throws_Exception()
        {
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(false);

            _userRepo.Setup(x => x.Create(It.IsAny<User>())).Throws(new Exception());

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.CreateAccount(_testEmail, _testPass);

            Assert.AreEqual(AccountResultCode.FailedToCreate, result.ResultCode);
        }

        [TestMethod]
        public void Test_CreateAccount_Creates_New_User()
        {
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(false);

            _userRepo.Setup(x => x.Create(It.IsAny<User>())).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.CreateAccount(_testEmail, _testPass);

            _userRepo.Verify();
        }

        [TestMethod]
        public void Test_CreateAccount_Returns_Success_On_Successful_User_Create()
        {
            _userRepo.Setup(x => x.UserExists(It.IsAny<string>())).Returns(false);

            _userRepo.Setup(x => x.Create(It.IsAny<User>()));

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.CreateAccount(_testEmail, _testPass);

            Assert.AreEqual(AccountResultCode.Success, result.ResultCode);
        }

        [TestMethod]
        public void Test_ResetPassword_Changes_Users_Password()
        {
            _passwordGenerator.Setup(x => x.GenerateNewPassword()).Returns(_testPass);

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(_testPass, "Salt")).Returns(_testHash).Verifiable();

            _userRepo.Setup(x => x.Save(It.Is<User>(u => u.PasswordHash == _testHash))).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ResetPassword(_testEmail);

            _userRepo.Verify();
        }

        [TestMethod]
        public void Test_ResetPassword_Returns_New_Password()
        {
            _passwordGenerator.Setup(x => x.GenerateNewPassword()).Returns(_testPass);

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(It.IsAny<string>(), It.IsAny<string>())).Returns(_testHash);

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ResetPassword(_testEmail);

            Assert.AreEqual(_testPass, result.NewPassword);
        }

        [TestMethod]
        public void Test_ResetPassword_Resets_Correct_User()
        {
            _userRepo.Setup(x => x.GetUserByEmail(_testEmail)).Returns(_testUser).Verifiable();

            _passwordGenerator.Setup(x => x.GenerateNewPassword()).Returns(_testPass);

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(It.IsAny<string>(), It.IsAny<string>())).Returns(_testHash);

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ResetPassword(_testEmail);

            _userRepo.Verify();
        }

        [TestMethod]
        public void Test_ChangePassword_Retrieves_Correct_User()
        {
            _userRepo.Setup(x => x.GetUserByEmail(_testEmail)).Returns(_testUser).Verifiable();

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(It.IsAny<string>(), It.IsAny<string>())).Returns(_testHash);

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ChangePassword(_testEmail, _testPass);

            _userRepo.Verify();
        }

        [TestMethod]
        public void Test_ChangePassword_Changes_Password_To_The_One_Provided()
        {
            _userRepo.Setup(x => x.GetUserByEmail(_testEmail)).Returns(_testUser);

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(_testPass, "Salt")).Returns(_testHash);

            _userRepo.Setup(x => x.Save(It.Is<User>(u => u.PasswordHash == _testHash))).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ChangePassword(_testEmail, _testPass);

            _userRepo.Verify();
        }

        [TestMethod]
        public void Test_ChangePassword_Sets_MustChangePassword_To_False()
        {
            _userRepo.Setup(x => x.GetUserByEmail(_testEmail)).Returns(_testUser);

            _cryptServices.Setup(x => x.GenerateSalt(It.IsAny<int>())).Returns("Salt");
            _cryptServices.Setup(x => x.HashPassword(_testPass, "Salt")).Returns(_testHash);

            _userRepo.Setup(x => x.Save(It.Is<User>(u => u.MustChangePassword == false))).Verifiable();

            var accountServices = new AccountService(_userRepo.Object, _cryptServices.Object, _passwordGenerator.Object);
            var result = accountServices.ChangePassword(_testEmail, _testPass);

            _userRepo.Verify();
        }
    }
}
